<h1>跨站请求伪造</h1>

<p><a href="http://en.wikipedia.org/wiki/Cross-site_request_forgery">跨站请求伪造(Cross-site request forgery)</a>， 简称为 XSRF，是 Web 应用中常见的一个安全问题。前面的链接也详细讲述了 XSRF 攻击的实现方式。</p>

<p>当前防范 XSRF 的一种通用的方法，是对每一个用户都记录一个无法预知的 cookie 数据，然后要求所有提交的请求（POST/PUT/DELETE）中都必须带有这个 cookie 数据。如果此数据不匹配 ，那么这个请求就可能是被伪造的。</p>

<p>beego 有内建的 XSRF 的防范机制，要使用此机制，你需要在应用配置文件中加上 <code>enablexsrf</code> 设定：</p>

<pre><code>enablexsrf = true
xsrfkey = 61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o
xsrfexpire = 3600
</code></pre>

<p>或者直接在 main 入口处这样设置：</p>

<pre><code>beego.EnableXSRF = true
beego.XSRFKEY = &quot;61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o&quot;
beego.XSRFExpire = 3600  //过期时间，默认1小时
</code></pre>

<p>如果开启了 XSRF，那么 beego 的 Web 应用将对所有用户设置一个 <code>_xsrf</code> 的 cookie 值（默认过期 1 小时），如果 <code>POST PUT DELET</code> 请求中没有这个 cookie 值，那么这个请求会被直接拒绝。如果你开启了这个机制，那么在所有被提交的表单中，你都需要加上一个域来提供这个值。你可以通过在模板中使用 专门的函数 <code>XSRFFormHTML()</code> 来做到这一点：</p>

<p>过期时间上面我们设置了全局的过期时间 <code>beego.XSRFExpire</code>，但是有些时候我们也可以在控制器中修改这个过期时间，专门针对某一类处理逻辑：</p>

<pre lang="go"><code>func (this *HomeController) Get(){
    this.XSRFExpire = 7200
    this.Data[&quot;xsrfdata&quot;]=template.HTML(this.XSRFFormHTML())
}
</code></pre>

<h3>在表单中使用</h3>

<p>在 Controller 中这样设置数据：</p>

<pre lang="go"><code>func (this *HomeController) Get(){
    this.Data[&quot;xsrfdata&quot;]=template.HTML(this.XSRFFormHTML())
}
</code></pre>

<p>然后在模板中这样设置：</p>

<pre><code>&lt;form action=&quot;/new_message&quot; method=&quot;post&quot;&gt;
  {{ .xsrfdata }}
  &lt;input type=&quot;text&quot; name=&quot;message&quot;/&gt;
  &lt;input type=&quot;submit&quot; value=&quot;Post&quot;/&gt;
&lt;/form&gt;
</code></pre>

<h3>在 JavaScript 中使用</h3>

<p>如果你提交的是 AJAX 的 POST 请求，你还是需要在每一个请求中通过脚本添加上 _xsrf 这个值。下面是在 AJAX 的 POST 请求，使用了 jQuery 函数来为所有请求都添加 _xsrf 值：</p>

<p>jQuery cookie插件：<a href="https://github.com/carhartl/jquery-cookie">https://github.com/carhartl/jquery-cookie</a><br />
    base64 插件：<a href="http://phpjs.org/functions/base64_decode/">http://phpjs.org/functions/base64_decode/</a></p>

<pre lang="js"><code>jQuery.postJSON = function(url, args, callback) {
   var xsrf, xsrflist;
   xsrf = $.cookie(&quot;_xsrf&quot;);
   xsrflist = xsrf.split(&quot;|&quot;);
   args._xsrf = base64_decode(xsrflist[0]);
    $.ajax({url: url, data: $.param(args), dataType: &quot;text&quot;, type: &quot;POST&quot;,
        success: function(response) {
        callback(eval(&quot;(&quot; + response + &quot;)&quot;));
    }});
};
</code></pre>

<h4>扩展 jQuery</h4>

<p>通过扩展 ajax 给每个请求加入 xsrf 的 header</p>

<p>需要你在 html 里保存一个 <code>_xsrf</code> 值</p>

<pre lang="go"><code>func (this *HomeController) Get(){
    this.Data[&quot;xsrf_token&quot;] = this.XSRFToken()
}
</code></pre>

<p>放在你的 head 中</p>

<pre lang="html"><code>&lt;head&gt;
    &lt;meta name=&quot;_xsrf&quot; content=&quot;{{.xsrf_token}}&quot; /&gt;
&lt;/head&gt;
</code></pre>

<p>扩展 ajax 方法，将 <code>_xsrf</code> 值加入 header，扩展后支持 jquery post/get 等内部使用了 ajax 的方法</p>

<pre lang="js"><code>var ajax = $.ajax;
$.extend({
    ajax: function(url, options) {
        if (typeof url === 'object') {
            options = url;
            url = undefined;
        }
        options = options || {};
        url = options.url;
        var xsrftoken = $('meta[name=_xsrf]').attr('content');
        var headers = options.headers || {};
        var domain = document.domain.replace(/\./ig, '\\.');
        if (!/^(http:|https:).*/.test(url) || eval('/^(http:|https:)\\/\\/(.+\\.)*' + domain + '.*/').test(url)) {
            headers = $.extend(headers, {'X-Xsrftoken':xsrftoken});
        }
        options.headers = headers;
        return ajax(url, options);
    }
});
</code></pre>

<p>对于 PUT 和 DELETE 请求（以及不使用将 form 内容作为参数的 POST 请求）来说，你也可以在 HTTP 头中以 X-XSRFToken 这个参数传递 XSRF token。</p>

<p>如果你需要针对每一个请求处理器定制 XSRF 行为，你可以重写 Controller 的 CheckXSRFCookie 方法。例如你需要使用一个不支持 cookie 的 API， 你可以通过将 <code>CheckXSRFCookie()</code> 函数设空来禁用 XSRF 保护机制。然而如果 你需要同时支持 cookie 和非 cookie 认证方式，那么只要当前请求是通过 cookie 进行认证的，你就应该对其使用 XSRF 保护机制，这一点至关重要。</p>

<h2>支持controller 级别的屏蔽</h2>

<p>XSRF 之前是全局设置的一个参数,如果设置了那么所有的 API 请求都会进行验证,但是有些时候API 逻辑是不需要进行验证的,因此现在支持在controller 级别设置屏蔽:</p>

<pre><code>type AdminController struct{
    beego.Controller
}

func (a *AdminController) Prepare() {
    a.EnableXSRF = false
}
</code></pre>
